%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  版权由杜文亮所有。                                                        %%
%%  本作品采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议授权。         %%
%%  您可访问 http://creativecommons.org/licenses/by-nc-sa/4.0/ 查看该许可条款。%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\commonfolder}{../../common-files}
\newcommand{\webcommon}{../Web_Common}

\input{\commonfolder/header}
\input{\commonfolder/copyright}


\lhead{\bfseries SEED 实验 -- 跨站请求伪造攻击实验}

\begin{document}

\begin{center}
{\LARGE 跨站请求伪造（CSRF）攻击实验}
\vspace{0.1in}\\
{\Large (Web 应用程序：\texttt{Elgg})}
\end{center}

\seedlabcopyright{2006 - 2020}


% *******************************************
% SECTION
% ******************************************* 
\section{概述}

本实验的目的是帮助学生理解跨站请求伪造（CSRF）攻击。CSRF 攻击涉及受害用户、受信任站点和恶意站点。当受害者在访问恶意站点时，恶意站点的网页就有机会向受信任站点发出 HTTP 请求，导致损害。

在这个实验中，学生们将使用 CSRF 攻击来攻击一个社交网络 Web 应用程序 \texttt{Elgg}。我们已经关闭了 \texttt{Elgg} 中的一些防护措施以进行本实验。该实验覆盖了以下主题：

\begin{itemize}[noitemsep]
    \item 跨站请求伪造攻击
    \item 防御 CSRF 的方法：秘密令牌和同源 cookie
    \item HTTP GET 和 POST 请求
    \item JavaScript 和 Ajax
\end{itemize}

\paragraph{参考资料。}
关于 CSRF 攻击的详细内容可以参见《SEED 书籍》第 10 章。

\paragraph{实验环境。}
\seedenvironmentB 
\nodependency



% *******************************************
% SECTION
% *******************************************
\section{实验环境搭建}

在本实验中，我们将使用三个网站。
第一个网站是一个社交网站 \url{www.seed-server.com}。第二个网站是攻击者的恶意站点，此网站可通过 \url{www.attacker32.com} 访问。第三个网站用于防御任务，其主机名为 \url{www.example32.com}。我们使用容器来搭建实验环境。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{容器设置与命令}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/container/setup}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{\texttt{Elgg} Web 应用程序}

我们在本实验中使用名为 Elgg 的开源 Web 应用程序。Elgg 是一款基于 Web 的社交网络应用程序。它已在提供的容器映像中设置完毕。我们使用两个容器，一个运行 Web 服务器 (\texttt{10.9.0.5})，另一个运行 MySQL 数据库 (\texttt{10.9.0.6})。这两个容器的 IP 地址在配置中已经固定，因此不要在 \texttt{docker-compose.yml} 文件中更改它们。


\paragraph{Elgg 容器。} 我们使用 Apache Web 服务器托管 Elgg Web 应用程序。网站设置包含在 Elgg 镜像文件夹内的 \texttt{apache\_elgg.conf} 中。配置指定网站的 URL 和存储 Web 应用程序代码的文件夹。

\begin{lstlisting}
    <VirtualHost *:80>
         DocumentRoot /var/www/elgg
         ServerName   www.seed-server.com
         <Directory /var/www/elgg>
              Options FollowSymlinks
              AllowOverride All
              Require all granted
         </Directory>
    </VirtualHost>
\end{lstlisting}

\paragraph{攻击者容器。}
我们使用另一个容器（\texttt{10.9.0.105}）来运行攻击者的网站，该网站包含一个恶意的 HTML 页面。此页面的 Apache 配置如下：

\begin{lstlisting}
    <VirtualHost *:80>
         DocumentRoot /var/www/attacker
         ServerName   www.attacker32.com
    </VirtualHost>
\end{lstlisting}

由于我们需要在容器内创建网页文件，为了方便，我们将主机上的 \texttt{Labsetup/attacker} 文件夹挂载到容器的 \texttt{/var/www/attacker} 文件夹中。因此，在 VM 中放置于 \texttt{attacker} 文件夹内的网页将被攻击者的网站托管，我们已经在该文件夹内放置了代码的框架。

\paragraph{DNS 配置。}
为了通过不同的 URL 访问 \texttt{Elgg} 网站、攻击者站点和防御站点，我们需要在 \texttt{/etc/hosts} 文件中添加以下条目，以便将这些主机名映射到其相应的 IP 地址。您需要使用 root 权限来更改此文件（使用 \texttt{sudo}）。需要注意的是，由于其他一些实验的原因，这些名称可能已经添加到文件中。如果它们映射到不同的 IP 地址，旧条目必须删除。

\begin{lstlisting}
    10.9.0.5        www.seed-server.com
    10.9.0.5        www.example32.com
    10.9.0.105      www.attacker32.com
\end{lstlisting}



% MySQL 数据库
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\webcommon/mysql}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\webcommon/elgg_accounts}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


% *******************************************
% SECTION
% ******************************************* 
\section{实验任务: 攻击}


% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务 1：观察 HTTP 请求}

在跨站请求伪造攻击中，我们需要伪造 HTTP 请求。因此，我们需要了解一个合法的 HTTP 请求长什么样以及它使用了哪些参数等信息。我们可以通过 Firefox 的扩展程序 \texttt{"HTTP Header Live"} 来实现这一目标。
本任务的目标是熟悉此工具。有关如何使用此工具的具体说明请参见指导部分~(\S~\ref{web:sec:httpheaderlive})。
请使用此工具捕获 \texttt{Elgg} 的 HTTP GET 和 POST 请求。在您的报告中，请识别这些请求中使用的参数。

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务 2：使用 GET 请求的 CSRF 攻击}

在这个任务中，我们需要使用 \texttt{Elgg} 社交网络中有两个用户：Alice 和 Samy。Samy 想要成为 Alice 的好友，但 Alice 并不想将他加到自己的好友列表中。Samy 决定使用 CSRF 攻击来实现这个目标。他会通过邮件或在 \texttt{Elgg} 里给 Alice 发一个 URL。当 Alice 好奇点击该 URL 后，她会被引导至 Samy 的网站：\url{www.attacker32.com}。请假装你是 Samy，描述你如何构建网页内容，以便当 Alice 访问此页面后，Samy 就会成为 Alice 好友列表中的成员（假设 Alice 当前的 \texttt{Elgg} 会话还是有效的）。

为了给受害者添加好友，我们需要知道合法的``添加好友''的 HTTP 请求（GET 请求）是什么样的。我们可以使用 \texttt{"HTTP Header Live"} 工具进行调查。在这个任务中，不允许编写 JavaScript 代码来发动 CSRF 攻击。您的工作是当 Alice 访问页面后攻击会立即发动，Alice 无需在页面上点击任何内容（提示：您可以使用 \texttt{img} 标签，它会自动触发 HTTP GET 请求）。

\texttt{Elgg} 实现了一种防御 CSRF 攻击的措施。在``添加好友''的 HTTP 请求中，您可能会注意到每个请求都包含了两个看起来很奇怪的参数，即 \texttt{\_\_elgg\_ts} 和 \texttt{\_\_elgg\_token}。这两个参数用于此防护措施，如果它们的值不正确，Elgg 则不会接受该请求。我们在本实验中关闭了这种防御措施，因此在伪造请求时无需包括这两个参数。

\paragraph{重要说明：} 如果您使用的是云虚拟机或非 SEED 的其他虚拟机，您的 VM 中的 Firefox 版本可能较新，并且可能会对跨站 cookie 采取一些限制。在较新的版本中，使用 \texttt{img} 标签来伪造 HTTP GET 请求将无法使攻击成功。这是因为浏览器对这类请求会阻止跨站 cookie。如果您遇到这种情况，您可以选择以下方法之一使攻击生效：

\begin{itemize}
    \item 不使用 \texttt{img} 标签，而是使用其他方式生成 GET 请求。例如，在下一个任务中我们使用 JavaScript 来伪造 POST 请求。我们可以使用相同的方法来伪造 GET 请求。
    \item 更改 Firefox 设置以取消这些限制。学生需要找出应该取消哪个限制条款。该限制设置在``隐私与安全''页面上，可以通过在 URL 场景中输入 \texttt{about:preferences\#privacy} 来查看。
\end{itemize}

如果您使用的是我们提供的 SEEDUbuntu 20.04 虚拟机，则此方法仍然有效，除非您已将 Firefox 升级到较新的版本。


% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务 3：使用 POST 请求的 CSRF 攻击}

在成功地将自己添加到 Alice 的好友列表后，Samy 想要做更多的事情。他希望 Alice 在她的个人资料中写着：``Samy 是我的英雄''。Alice 并不喜欢 Samy，更不用提把这句话放到她的个人资料中了。Samy 计划使用 CSRF 攻击来实现这个目标，这也是本任务的目的。

一种攻击的方法是给 Alice 的 \texttt{Elgg} 帐户发一条消息发，希望她点击该消息中的 URL。这个 URL 会引导 Alice 来到 Samy（即您）的恶意网站 \url{www.attacker32.com}，在那里您可以发动 CSRF 攻击。

您的攻击目标是修改受害者的个人资料。具体来说，攻击者需要伪造一个请求来修改 Elgg 中受害用户的个人资料信息。
允许用户自行修改个人资料是 Elgg 的功能之一。如果用户想要修改自己的个人资料，他们可以访问 Elgg 的个人资料页面，填写表格，并提交表单到服务器脚本中，也就是发送一个 POST 请求到 \texttt{/profile/edit.php}，该脚本会处理请求并执行个人资料的修改。

服务器端脚本 \texttt{edit.php} 同时接受 GET 和 POST 请求，因此您可以使用与任务 1 中相同的方法来实现攻击。然而，在这个任务中，您需要使用 POST 请求。即，当受害者访问其恶意站点时，攻击者（也就是您）需要伪造一个来自受害者浏览器的 HTTP POST 请求。攻击者需要了解此类请求的结构。通过对个人资料进行一些修改并使用 \texttt{"HTTP Header Live"} 工具来监控请求，您可以观察到和下面类似的请求。与 GET 请求将参数附加在 URL 字符串中不同，POST 请求中的参数包含在 HTTP 消息体中（见两个 \ding{80} 符号之间的内容）：


\begin{lstlisting}
http://www.seed-server.com/action/profile/edit

POST /action/profile/edit HTTP/1.1
Host: www.seed-server.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:23.0) ...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://www.seed-server.com/profile/elgguser1/edit
Cookie: Elgg=p0dci8baqrl4i2ipv2mio3po05
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 642
__elgg_token=fc98784a9fbd02b68682bbb0e75b428b&__elgg_ts=1403464813  (*@\ding{80}@*)
name=elgguser1&description=%3Cp%3Iamelgguser1%3C%2Fp%3E
accesslevel%5Bdescription%5D=2&briefdescription= Iamelgguser1
accesslevel%5Bbriefdescription%5D=2&location=US
......                                                              (*@\ding{80}@*)
\end{lstlisting}

理解了请求的结构之后，您需要在攻击页面里生成这样的请求。为了帮助您编写此类 JavaScript 程序，我们提供了一个示例代码。您可以使用此示例代码来构建 CSRF 攻击，但这只是个示例代码，您需要对其进行修改以使其适用于您的攻击。

\begin{lstlisting}
<html>
<body>
<h1>此页面伪造一个 HTTP POST 请求。</h1>
<script type="text/javascript">

function forge_post()
{
    var fields;

    // 下面是攻击者需要填写的表单输入项。这些条目是隐藏的，受害者看不到。
    fields += "<input type='hidden' name='name' value='****'>";
    fields += "<input type='hidden' name='briefdescription' value='****'>";
    fields += "<input type='hidden' name='accesslevel[briefdescription]' 
                                    value='2'>";                         (*@\ding{192}@*)
    fields += "<input type='hidden' name='guid' value='****'>";

    // 创建一个 <form> 元素。
    var p = document.createElement("form");

    // 构造表单
    p.action = "http://www.example.com";
    p.innerHTML = fields;
    p.method = "post";

    // 将表单添加到当前页面中。
    document.body.appendChild(p);

    // 提交表单
    p.submit();
}

	
// 页面加载后会调用 forge_post() 函数。
window.onload = function() { forge_post();}
</script>
</body>
</html>
\end{lstlisting}

在 Line~\ding{192}，值 \texttt{2} 将字段的访问级别设置为公开，这是必须的，否则默认情况下该字段将被设为私有，其他用户则无法看到此字段。需要注意的是，在从 PDF 文件中复制和粘贴上述代码时，程序中的单引号字符可能变成了其他符号（但仍然看起来像单引号）。这会导致语法错误。用您的键盘输入的单引号替换所有这些符号将解决这些问题。

\paragraph{问题：}
除了详细描述您的攻击外，您还需要在报告中回答以下问题：

\begin{itemize}
   \item \textbf{问题 1}：伪造的 HTTP 请求需要 Alice 的用户 ID（guid）才能正常工作。如果 Boby 特别针对 Alice 发动攻击，在发动攻击前他可以想办法来获取 Alice 的用户 ID。Boby 不知道 Alice 在 \texttt{Elgg} 中的密码，因此无法登录到 Alice 的帐户以获取相关信息。请描述 Boby 如何解决这个问题。

   
   \item \textbf{问题 2：} 如果 Boby 想要修改访问他的恶意网页的任何人的 Elgg 个人资料，在事先并不知道谁会访问的情况下还可以发起 CSRF 攻击吗？请解释。
\end{itemize}

% *******************************************
% SECTION
% *******************************************
\section{防御任务} 

CSRF 不难防御。最初，大多数应用程序都会在他们的网页中嵌入一个秘密令牌，并通过检查请求中是否存在该令牌来判断请求是同源请求还是跨站请求。这称为``秘密令牌''方法。最近，大多数浏览器都实现了一种名为``同源 cookie''的机制，旨在简化 CSRF 防御措施的实现。我们将对这两种方法进行实验。

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务 4：启用 Elgg 的防护措施} 

为了防御 CSRF 攻击，Web 应用程序可以将秘密令牌嵌入到页面中。所有来自这些页面的请求都必须携带此令牌，
否则会被视为跨站请求，不会具有和同源请求相同的权限。攻击者无法获取此秘密令牌，因此他们的请求很容易被识别为跨站请求。

\texttt{Elgg} 使用这种秘密令牌方法作为其内置的防护措施来防御 CSRF 攻击。我们已经关闭了这些防护措施以使攻击生效。\texttt{Elgg} 在请求中嵌入了两个参数 \texttt{\_\_elgg\_ts} 和 \texttt{\_\_elgg\_token}。服务器在处理请求前会验证它们。

\paragraph{将秘密令牌和时间戳嵌入到网页中：}
Elgg 在所有需要用户操作的地方都添加了安全令牌和时间戳。以下 HTML 代码出现在所有的表里。
这两个隐藏字段会在表单提交时被附加到请求中：

\begin{lstlisting}
<input type = "hidden" name = "__elgg_ts" value = "" />
<input type = "hidden" name = "__elgg_token" value = "" />
\end{lstlisting}

Elgg 还将安全令牌和时间戳的值赋给 JavaScript 变量，以便页面上的 JavaScript 代码可以轻松访问这些变量。

\begin{lstlisting}
elgg.security.token.__elgg_ts;
elgg.security.token.__elgg_token;
\end{lstlisting}

在 \texttt{Elgg} 的网页中添加安全令牌和时间戳是通过 \path{vendor/elgg/elgg/views/default/input/securitytoken.php} 来实现的。下面的代码片段展示了这些内容是如何动态地添加到页面上的。

\begin{lstlisting}
$ts = time();
$token = elgg()->csrf->generateActionToken($ts);

echo elgg_view('input/hidden', ['name' => '__elgg_token', 'value' => $token]);
echo elgg_view('input/hidden', ['name' => '__elgg_ts', 'value' => $ts]);
\end{lstlisting}


\paragraph{秘密令牌的生成。}
Elgg 的安全令牌是下面信息产生的哈希值：网站提供的秘密值，时间戳、用户会话 ID 和随机生成的会话字符串。下面的代码展示了 Elgg 中的安全令牌的生成过程（在 \path{vendor/elgg/elgg/engine/classes/Elgg/Security/Csrf.php} 中）。

\begin{lstlisting}
/**
 * 从会话令牌、时间戳和站点密钥生成一个令牌。
 */
public function generateActionToken($timestamp, $session_token = '') {
  if (!$session_token) {
          $session_token = $this->session->get('__elgg_session');
          if (!$session_token) {
                  return false;
          }
  }

  return $this->hmac
          ->getHmac([(int) $timestamp, $session_token], 'md5')
          ->getToken();
}
\end{lstlisting}


\paragraph{秘密令牌验证。}
\texttt{Elgg} 网站会验证生成的令牌和时间戳以防御 CSRF 攻击。每当用户执行某个操作时，都会调用 \texttt{Csrf.php} 中的 \texttt{validate()} 函数，此函数会对这些令牌进行验证。
如果令牌不存在或无效，则将拒绝该操作并将用户重定向到其他页面。我们在该函数开始添加了一个 \texttt{return} 语句，从而禁用了这一功能。

\begin{lstlisting}
public function validate(Request $request) {
   (*@\textbf{return;}@*) // 为 SEED 实验（禁用 CSRF 防护措施）而添加

    $token = $request->getParam('__elgg_token');
    $ts = $request->getParam('__elgg_ts');
    ... (省略了代码)
}
\end{lstlisting}

\paragraph{任务：开启防护措施。} 
为了启用防护措施，请进入 \texttt{Elgg} 容器，前往 \path{/var/www/elgg/vendor/elgg/elgg/engine/classes/Elgg/Security} 文件夹，从 \texttt{Csrf.php} 中删除 \texttt{return} 语句。容器内有一个简单的编辑器叫做 \texttt{nano}。在完成更改后，请重新运行攻击，并观察您的攻击是否成功。请指出捕获的 HTTP 请求中的秘密令牌。请解释为什么攻击者无法在 CSRF 攻击中发送这些秘密令牌；是什么阻止了他们从网页中找到这些秘密令牌？

需要注意的是（非常重要），当我们启用防护措施后进行CSRF攻击去修改个人资料时，攻击失败后攻击者的页面会被重新载入，这将再次触发伪造的 POST 请求。这会导致另一个失败的攻击，然后页面将继续被重新加载并发送出另一个伪造的 POST 请求。这种无限循环可能会导致您的计算机变慢。因此，在确认攻击失败后，请关闭页面以停止无限循环。

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务 5：实验同源 cookie 方法} 

大多数浏览器现在都实现了一种称为``同源 cookie''的机制，这是与 cookie 相关的一个属性。
当发送请求时，浏览器会检查这个属性，并决定是否在跨站请求中附加此 cookie。如果一个 Web 应用程序不希望某个 cookie 用于跨站请求，他们可以标记该 cookie 为同源。例如应用程序可以把 session ID cookie 设为同源 cookie，从而阻止任何跨站请求使用 session ID，因此无法发动 CSRF 攻击。

为了帮助学生了解如何利用同源 cookie 来防御 CSRF 攻击，我们在一个容器中创建了一个名为 \url{www.example32.com} 的网站。请访问以下 URL。我们已将主机名映射到 \texttt{10.9.0.5}。

\begin{lstlisting}
URL: http://www.example32.com/
\end{lstlisting}

一旦您访问过这个网站，您的浏览器将设置三个 cookie：\texttt{cookie-normal}, \texttt{cookie-lax} 和 \texttt{cookie-strict}。如名称所示，第一个 cookie 是一个普通的 cookie；第二个和第三个是两种类型的同源 cookie（Lax 类型和 Strict 类型）。我们设计了两组实验来测试哪些 cookie 会被发送给服务器。

请点击网页中的链接。链接 A 指向 \url{example32.com} 的页面，而链接 B 则指向 \url{attacker32.com} 的页面。这两个页面几乎是相同的（除了背景颜色），并且它们都发送了三种不同类型的请求到 \url{www.example32.com/showcookies.php}，该页面仅仅显示浏览器发送的 cookie。通过查看显示结果，您可以知道哪些 cookie 被发送。请完成以下任务：

\begin{itemize}
  \item 请描述您看到的内容并解释在某些场景下为何有些 cookie 不会被发送。
  
  \item 根据您的理解，请描述同源 cookie 如何帮助服务器检测请求是跨站还是同源的。
  
  \item 请描述如何使用同源 cookie 机制来帮助 Elgg 防御 CSRF 攻击。您只需描述一些基本想法，无需实现它们。
\end{itemize}

\paragraph{额外加分：} 尽管这不是强制要求的，我们鼓励学生修改 \texttt{Elgg} 应用程序以利用同源 cookie 机制来防御 CSRF 攻击。建议讲师给予那些成功完成此任务的学生额外加分。学生应该与他们的导师讨论额外得分事宜。

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\section{指导方针}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\webcommon/Web_Dev_Tools}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


% *******************************************
% SECTION
% ******************************************* 
\section{提交}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/submission}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\end{document}
